﻿Option Strict On
Option Explicit On

Imports System.Drawing

''' <summary>
''' Defines a two-dimensional vector with X,Y coordinates and provides a variety of helper methods.
''' </summary>
''' <remarks></remarks>
Public Structure Vector2
    Implements IEquatable(Of Vector2)
    Implements IComparable(Of Vector2)
    Implements IComparable

    ''' <summary>
    ''' An empty Vector2 (0,0).
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly Empty As New Vector2

    ''' <summary>
    ''' Determines if the area of the three parameter vectors is within the tolerance specified.
    ''' </summary>
    ''' <param name="vectorA">The first point of the area to measure.</param>
    ''' <param name="vectorB">The second point of the area to measure.</param>
    ''' <param name="vectorC">The third point of the area to measure.</param>
    ''' <param name="tolerance">The +/- tolerance allowable for the measured area.</param>
    ''' <returns>True if the vectors are collinear, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Shared Function AreVectorsCollinear(ByVal vectorA As Vector2, ByVal vectorB As Vector2, ByVal vectorC As Vector2, ByVal tolerance As Single) As Boolean
        Return Geometry.WithinRange(GetArea(vectorA, vectorB, vectorC), -tolerance, tolerance)
    End Function

    ''' <summary>
    ''' Determines the angle in degrees between two vectors.
    ''' </summary>
    ''' <param name="source">The source vector to measure from.</param>
    ''' <param name="target">The target vector to measure to.</param>
    ''' <returns>The angle from source to target.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetAngleTo(ByVal source As Vector2, ByVal target As Vector2) As Single
        Return Geometry.RadiansToDegrees(Vector2.GetRadiansTo(source, target))
    End Function

    ''' <summary>
    ''' Gets the area specified by three points.
    ''' </summary>
    ''' <param name="vectorA">The first point of the area to measure.</param>
    ''' <param name="vectorB">The second point of the area to measure.</param>
    ''' <param name="vectorC">The third point of the area to measure.</param>
    ''' <returns>The area of the three vectors.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetArea(ByVal vectorA As Vector2, ByVal vectorB As Vector2, ByVal vectorC As Vector2) As Single
        Return vectorA.X * (vectorB.Y - vectorC.Y) + vectorB.X * (vectorC.Y - vectorA.Y) + vectorC.X * (vectorA.Y - vectorB.Y)
    End Function

    ''' <summary>
    ''' Gets the cross product of two vectors (source.x * target.y - source.y * target.x).
    ''' </summary>
    ''' <param name="source">The first vector.</param>
    ''' <param name="target">The second vector.</param>
    ''' <returns>The cross product of the two vectors.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetCrossProduct(ByVal source As Vector2, ByVal target As Vector2) As Single
        Return source.X * target.Y - source.Y * target.X
    End Function

    ''' <summary>
    ''' Calculates the distance between two vectors. This value is always positive.
    ''' </summary>
    ''' <param name="source">The source vector to measure from.</param>
    ''' <param name="target">The target vector to measure to.</param>
    ''' <returns>The distance between the two vectors.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetDistance(ByVal source As Vector2, ByVal target As Vector2) As Single
        Dim squareX As Double = CDbl(target.X - source.X)
        squareX *= squareX
        Dim squareY As Double = CDbl(target.Y - source.Y)
        squareY *= squareY
        Return CSng(Math.Sqrt(squareX + squareY))
    End Function

    ''' <summary>
    ''' Gets the dot product of two vectors (source.x * target.x + source.y * target.y).
    ''' </summary>
    ''' <param name="source">The first vector.</param>
    ''' <param name="target">The second vector.</param>
    ''' <returns>The dot-product of the two vectors.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetDotProduct(ByVal source As Vector2, ByVal target As Vector2) As Single
        Return source.X * target.X + source.Y * target.Y
    End Function

    ''' <summary>
    ''' Gets the magnitude of a vector (sqrt[x^2 + y^2]).
    ''' </summary>
    ''' <param name="source">The vector to measure.</param>
    ''' <returns>The magnitude of the vector.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetMagnitude(ByVal source As Vector2) As Single
        Return Geometry.Sqrt(Geometry.Square(source.X) + Geometry.Square(source.Y))
    End Function

    ''' <summary>
    ''' Gets the normalized version of a vector (x/magnitude, y/magnitude).
    ''' </summary>
    ''' <param name="source">The vector to normalize.</param>
    ''' <returns>The normalized vector.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetNormalized(ByVal source As Vector2) As Vector2
        Dim delta As Single = Vector2.GetMagnitude(source)
        Return New Vector2(source.X / delta, source.Y / delta)
    End Function

    ''' <summary>
    ''' Determines the angle in radians between two vectors.
    ''' </summary>
    ''' <param name="source">The source vector to measure from.</param>
    ''' <param name="target">The target vector to measure to.</param>
    ''' <returns>The angle, in radians, between the two vectors.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetRadiansTo(ByVal source As Vector2, ByVal target As Vector2) As Single
        Return Geometry.WrapAngle(Geometry.Atan2(target.Y - source.Y, target.X - source.X))
    End Function

    ''' <summary>
    ''' Calculates a vector which is the specified distance from the source, at the angle specified.
    ''' </summary>
    ''' <param name="source">The source vector to calculate from.</param>
    ''' <param name="angle">The angle to calculate at, in radians.</param>
    ''' <param name="distance">The distance from the source.</param>
    ''' <returns>A Vector2 position the specified distance and angle from the source.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetVectorAtAngle(ByVal source As Vector2, angle As Single, distance As Single) As Vector2
        Return source + New Vector2(CSng(Math.Cos(angle) * distance), CSng(Math.Sin(angle) * distance))
    End Function

    ''' <summary>
    ''' Calculates a vector which is the specified distance from source to target, at the angle between source and target.
    ''' Set an object's position to the result of this method to move that object toward (positive distance) or
    ''' away from (negative distance) another target object.
    ''' </summary>
    ''' <param name="source">The source vector to calculate from.</param>
    ''' <param name="target">The destination vector to calculate toward.</param>
    ''' <param name="distance">The distance to calculate. Use positive values to move toward target and negative to move away from target.</param>
    ''' <returns>The Vector2 which is the specified distance toward or away from the target.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetVectorToward(ByVal source As Vector2, ByVal target As Vector2, ByVal distance As Single) As Vector2
        Dim angle As Single = Vector2.GetRadiansTo(source, target)
        Return source + New Vector2(Geometry.Cos(angle) * distance, Geometry.Sin(angle) * distance)
    End Function

    ''' <summary>
    ''' The X component value of this vector.
    ''' </summary>
    ''' <remarks></remarks>
    Public X As Single

    ''' <summary>
    ''' The Y component value of this vector.
    ''' </summary>
    ''' <remarks></remarks>
    Public Y As Single

    ''' <summary>
    ''' The horizontal or x-axis component of the vector.
    ''' </summary>
    ''' <value>Sets the horizontal or x-axis component of the vector.</value>
    ''' <returns>Gets the horizontal or x-axis component of the vector.</returns>
    ''' <remarks></remarks>
    Public Property Horizontal As Single
        Get
            Return X
        End Get
        Set(ByVal value As Single)
            X = value
        End Set
    End Property

    ''' <summary>
    ''' The vertical or y-axis component of the vector.
    ''' </summary>
    ''' <value>Sets the vertical or y-axis component of the vector.</value>
    ''' <returns>Gets the vertical or y-axis component of the vector.</returns>
    ''' <remarks></remarks>
    Public Property Vertical As Single
        Get
            Return Y
        End Get
        Set(ByVal value As Single)
            Y = value
        End Set
    End Property

    Public Sub New(ByVal xValue As Single, ByVal yValue As Single)
        X = xValue
        Y = yValue
    End Sub

    Public Sub New(ByVal bothValues As Single)
        Me.New(bothValues, bothValues)
    End Sub

    Public Sub New(ByVal pointValue As PointF)
        Me.New(pointValue.X, pointValue.Y)
    End Sub

    Public Sub New(ByVal pointValue As Point)
        Me.New(CSng(pointValue.X), CSng(pointValue.Y))
    End Sub

    ''' <summary>
    ''' Gets the distance to a target vector.
    ''' </summary>
    ''' <param name="target">The target to measure the distance to.</param>
    ''' <returns>The distance from this vector to the target.</returns>
    ''' <remarks></remarks>
    Public Function DistanceTo(ByVal target As Vector2) As Single
        Return Vector2.GetDistance(Me, target)
    End Function

    ''' <summary>
    ''' Gets the angle in degrees between this vector and a target.
    ''' </summary>
    ''' <param name="target">The target vector to measure the angle between.</param>
    ''' <returns>The angle between this vector and the target.</returns>
    ''' <remarks></remarks>
    Public Function AngleTo(ByVal target As Vector2) As Single
        Return Vector2.GetAngleTo(Me, target)
    End Function

    ''' <summary>
    ''' Gets the angle in radians between this vector and a target.
    ''' </summary>
    ''' <param name="target">The garget vector to measure the angle between.</param>
    ''' <returns>The angle between this vector and the target.</returns>
    ''' <remarks></remarks>
    Public Function RadiansTo(ByVal target As Vector2) As Single
        Return Vector2.GetRadiansTo(Me, target)
    End Function

    ''' <summary>
    ''' Gets a vector which is the specified distance from this vector, at an angle toward the target (or away from the target with negative values for distance).
    ''' </summary>
    ''' <param name="target">The target vector to calculate toward.</param>
    ''' <param name="distance">The distance to move toward the target. Use a negative value to move away from the target.</param>
    ''' <returns>A vector which is the specified distance toward the target.</returns>
    ''' <remarks></remarks>
    Public Function VectorToward(ByVal target As Vector2, ByVal distance As Single) As Vector2
        Return Vector2.GetVectorToward(Me, target, distance)
    End Function

    ''' <summary>
    ''' Gets the magnitude of this vector.
    ''' </summary>
    ''' <returns>This vector's magnitude.</returns>
    ''' <remarks></remarks>
    Public Function Magnitude() As Single
        Return Vector2.GetMagnitude(Me)
    End Function

    ''' <summary>
    ''' Gets the normalized version of this vector.
    ''' </summary>
    ''' <returns>The normalization of this vector.</returns>
    ''' <remarks></remarks>
    Public Function Normalize() As Vector2
        Return Vector2.GetNormalized(Me)
    End Function

    ''' <summary>
    ''' Converts this vector to a Point structure.
    ''' </summary>
    ''' <returns>A System.Point representing this vector.</returns>
    ''' <remarks></remarks>
    Public Function ToPoint() As Point
        Return New Point(CInt(X), CInt(Y))
    End Function

    ''' <summary>
    ''' Converts this vector to a PointF structure.
    ''' </summary>
    ''' <returns>A System.PointF representing this vector.</returns>
    ''' <remarks></remarks>
    Public Function ToPointF() As PointF
        Return New PointF(X, Y)
    End Function

    ''' <summary>
    ''' Returns the string representation of this vector in the format [X=n.nn/Y=n.nn]
    ''' </summary>
    ''' <returns>The string representation of this vector.</returns>
    ''' <remarks></remarks>
    Public Overrides Function ToString() As String
        Return String.Format("[X={0:n2}/Y={1:n2}]", X, Y)
    End Function

    ''' <summary>
    ''' Returns the string representation of this vector using the supplied string format,
    ''' where {0} is supplied with the X field value and {1} is supplied with the Y field value.
    ''' </summary>
    ''' <param name="format">The format string to use ({0}=X, {1}=Y).</param>
    ''' <returns>The formatted string representation of this vector.</returns>
    ''' <remarks></remarks>
    Public Overloads Function ToString(ByVal format As String) As String
        Return String.Format(format, X, Y)
    End Function

    Public Shared Operator =(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.X = target.X AndAlso source.Y = target.Y
    End Operator

    Public Shared Operator <>(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return Not (source = target)
    End Operator

    Public Shared Operator +(ByVal source As Vector2, ByVal target As Vector2) As Vector2
        Return New Vector2(source.X + target.X, source.Y + target.Y)
    End Operator

    Public Shared Operator -(ByVal source As Vector2, ByVal target As Vector2) As Vector2
        Return New Vector2(source.X - target.X, source.Y - target.Y)
    End Operator

    Public Shared Operator *(ByVal source As Vector2, ByVal target As Vector2) As Vector2
        Return New Vector2(source.X * target.X, source.Y * target.Y)
    End Operator

    Public Shared Operator /(ByVal source As Vector2, ByVal target As Vector2) As Vector2
        Return New Vector2(source.X / target.X, source.Y / target.Y)
    End Operator

    Public Shared Operator +(ByVal source As Vector2, ByVal target As Single) As Vector2
        Return New Vector2(source.X + target, source.Y + target)
    End Operator

    Public Shared Operator -(ByVal source As Vector2, ByVal target As Single) As Vector2
        Return New Vector2(source.X - target, source.Y - target)
    End Operator

    Public Shared Operator *(ByVal source As Vector2, ByVal target As Single) As Vector2
        Return New Vector2(source.X * target, source.Y * target)
    End Operator

    Public Shared Operator /(ByVal source As Vector2, ByVal target As Single) As Vector2
        Return New Vector2(source.X / target, source.Y / target)
    End Operator

    Public Shared Operator >(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.CompareTo(target) > 0
    End Operator

    Public Shared Operator <(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.CompareTo(target) < 0
    End Operator

    Public Shared Operator >=(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.CompareTo(target) >= 0
    End Operator

    Public Shared Operator <=(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.CompareTo(target) <= 0
    End Operator

    Public Shared Narrowing Operator CType(ByVal source As Vector2) As PointF
        Return source.ToPointF
    End Operator

    Public Shared Narrowing Operator CType(ByVal source As Vector2) As Point
        Return source.ToPoint
    End Operator

    Public Shared Widening Operator CType(ByVal source As PointF) As Vector2
        Return New Vector2(source.X, source.Y)
    End Operator

    Public Shared Widening Operator CType(ByVal source As Point) As Vector2
        Return New Vector2(CSng(source.X), CSng(source.Y))
    End Operator

    Public Overloads Function Equals(ByVal other As Vector2) As Boolean Implements System.IEquatable(Of Vector2).Equals
        Return Me = other
    End Function

    ''' <summary>
    ''' Compares the magnitude of this vector to another vector.
    ''' </summary>
    ''' <param name="other">The vector to compare to.</param>
    ''' <returns>The difference in magnitude between vectors.</returns>
    ''' <remarks></remarks>
    Public Function CompareTo(ByVal other As Vector2) As Integer Implements System.IComparable(Of Vector2).CompareTo
        Dim thisMag As Single = Magnitude()
        Dim otherMag As Single = other.Magnitude()
        If thisMag = otherMag Then
            Return (X + Y).CompareTo(other.X + other.Y)
        End If
        Return thisMag.CompareTo(otherMag)
    End Function

    ''' <summary>
    ''' Compares this vector to the target object using magnitude comparison if the target is a vector, and
    ''' converting Point and PointF to vectors. Any non-vector comparison returns -1.
    ''' </summary>
    ''' <param name="obj">The object to compare to</param>
    ''' <returns>The difference in magnitude between vectors if obj is Vector2, Point or PointF, otherwise -1.</returns>
    ''' <remarks></remarks>
    Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
        If TypeOf obj Is Vector2 Then
            Return CompareTo(DirectCast(obj, Vector2))
        ElseIf TypeOf obj Is PointF Then
            Return CompareTo(New Vector2(DirectCast(obj, PointF)))
        ElseIf TypeOf obj Is Point Then
            Return CompareTo(New Vector2(DirectCast(obj, Point)))
        Else
            Return -1
        End If
    End Function
End Structure